home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / ANSI / c-client / imap2.c < prev    next >
C/C++ Source or Header  |  1996-05-23  |  68KB  |  2,094 lines

  1. /*
  2.  * Program:    Interactive Mail Access Protocol 2 (IMAP2) routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    15 June 1988
  13.  * Last Edited:    23 May 1996
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1996 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. #include "mail.h"
  48. #include "osdep.h"
  49. #include "imap2.h"
  50. #include "misc.h"
  51.  
  52. /* Driver dispatch used by MAIL */
  53.  
  54. DRIVER imapdriver = {
  55.   "imap2",            /* driver name */
  56.   (DRIVER *) NIL,        /* next driver */
  57.   map_valid,            /* mailbox is valid for us */
  58.   map_parameters,        /* manipulate parameters */
  59.   map_find,            /* find mailboxes */
  60.   map_find_bboards,        /* find bboards */
  61.   map_find_all,            /* find all mailboxes */
  62.   map_find_all_bboards,        /* find all bboards */
  63.   map_subscribe,        /* subscribe to mailbox */
  64.   map_unsubscribe,        /* unsubscribe from mailbox */
  65.   map_subscribe_bboard,        /* subscribe to bboard */
  66.   map_unsubscribe_bboard,    /* unsubscribe from bboard */
  67.   map_create,            /* create mailbox */
  68.   map_delete,            /* delete mailbox */
  69.   map_rename,            /* rename mailbox */
  70.   map_open,            /* open mailbox */
  71.   map_close,            /* close mailbox */
  72.   map_fetchfast,        /* fetch message "fast" attributes */
  73.   map_fetchflags,        /* fetch message flags */
  74.   map_fetchstructure,        /* fetch message envelopes */
  75.   map_fetchheader,        /* fetch message header only */
  76.   map_fetchtext,        /* fetch message body only */
  77.   map_fetchbody,        /* fetch message body section */
  78.   map_setflag,            /* set message flag */
  79.   map_clearflag,        /* clear message flag */
  80.   map_search,            /* search for message based on criteria */
  81.   map_ping,            /* ping mailbox to see if still alive */
  82.   map_check,            /* check for new messages */
  83.   map_expunge,            /* expunge deleted messages */
  84.   map_copy,            /* copy messages to another mailbox */
  85.   map_move,            /* move messages to another mailbox */
  86.   map_append,            /* append string message to mailbox */
  87.   map_gc            /* garbage collect stream */
  88. };
  89.  
  90.                 /* prototype stream */
  91. MAILSTREAM imapproto = {&imapdriver};
  92.  
  93.                 /* driver parameters */
  94. static long map_maxlogintrials = MAXLOGINTRIALS;
  95. static long map_lookahead = MAPLOOKAHEAD;
  96. static long map_port = 0;
  97. static long map_prefetch = MAPLOOKAHEAD;
  98. static long map_loginfullname = NIL;
  99. static long map_closeonerror = NIL;
  100.  
  101. /* Mail Access Protocol validate mailbox
  102.  * Accepts: mailbox name
  103.  * Returns: our driver if name is valid, NIL otherwise
  104.  */
  105.  
  106. DRIVER *map_valid (char *name)
  107. {
  108.   return mail_valid_net (name,&imapdriver,NIL,NIL);
  109. }
  110.  
  111.  
  112. /* Mail Access Protocol manipulate driver parameters
  113.  * Accepts: function code
  114.  *        function-dependent value
  115.  * Returns: function-dependent return value
  116.  */
  117.  
  118. void *map_parameters (long function,void *value)
  119. {
  120.   switch ((int) function) {
  121.   case SET_MAXLOGINTRIALS:
  122.     map_maxlogintrials = (long) value;
  123.     break;
  124.   case GET_MAXLOGINTRIALS:
  125.     value = (void *) map_maxlogintrials;
  126.     break;
  127.   case SET_LOOKAHEAD:
  128.     map_lookahead = (long) value;
  129.     break;
  130.   case GET_LOOKAHEAD:
  131.     value = (void *) map_lookahead;
  132.     break;
  133.   case SET_IMAPPORT:
  134.     map_port = (long) value;
  135.     break;
  136.   case GET_IMAPPORT:
  137.     value = (void *) map_port;
  138.     break;
  139.   case SET_PREFETCH:
  140.     map_prefetch = (long) value;
  141.     break;
  142.   case GET_PREFETCH:
  143.     value = (void *) map_prefetch;
  144.     break;
  145.   case SET_LOGINFULLNAME:
  146.     map_loginfullname = (long) value;
  147.     break;
  148.   case GET_LOGINFULLNAME:
  149.     value = (void *) map_loginfullname;
  150.     break;
  151.   case SET_CLOSEONERROR:
  152.     map_closeonerror = (long) value;
  153.     break;
  154.   case GET_CLOSEONERROR:
  155.     value = (void *) map_closeonerror;
  156.     break;
  157.   default:
  158.     value = NIL;        /* error case */
  159.     break;
  160.   }
  161.   return value;
  162. }
  163.  
  164. /* Mail Access Protocol find list of mailboxes
  165.  * Accepts: mail stream
  166.  *        pattern to search
  167.  */
  168.  
  169. void map_find (MAILSTREAM *stream,char *pat)
  170. {
  171.   void *s = NIL;
  172.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  173.   if (stream) {            /* have a mailbox stream open? */
  174.                 /* begin with a host specification? */
  175.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  176.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  177.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  178.       strcpy (tmp,pat);        /* copy host name */
  179.       tmp[patx-pat] = '\0';    /* tie off prefix */
  180.       LOCAL->prefix = cpystr (tmp);
  181.     }
  182.     else patx = pat;        /* use entire specification */
  183.     if (LOCAL && LOCAL->use_find &&
  184.     !strcmp (imap_sendq1 (stream,"FIND MAILBOXES",patx)->key,"BAD"))
  185.       LOCAL->use_find = NIL;
  186.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  187.   }
  188.   else while (t = sm_read (&s))    /* read subscription database */
  189.     if ((*t != '*') && mail_valid_net (t,&imapdriver,NIL,NIL) && pmatch(t,pat))
  190.       mm_mailbox (t);
  191. }
  192.  
  193. /* Mail Access Protocol find list of bboards
  194.  * Accepts: mail stream
  195.  *        pattern to search
  196.  */
  197.  
  198. void map_find_bboards (MAILSTREAM *stream,char *pat)
  199. {
  200.   void *s = NIL;
  201.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  202.   if (stream) {            /* have a mailbox stream open? */
  203.                 /* begin with a host specification? */
  204.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  205.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  206.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  207.       strcpy (tmp,pat);        /* copy host name */
  208.       tmp[patx-pat] = '\0';    /* tie off prefix */
  209.       LOCAL->prefix = cpystr (tmp);
  210.     }
  211.     else patx = pat;        /* use entire specification */
  212.     if (stream && LOCAL && LOCAL->use_find && LOCAL->use_bboard &&
  213.     !strcmp (imap_sendq1 (stream,"FIND BBOARDS",patx)->key,"BAD"))
  214.       LOCAL->use_bboard = NIL;
  215.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  216.   }
  217.   else while (t = sm_read (&s))    /* read subscription database */
  218.     if ((*t == '*') && mail_valid_net (++t,&imapdriver,NIL,NIL) &&
  219.     pmatch (t,pat)) mm_bboard (t);
  220. }
  221.  
  222. /* Mail Access Protocol find list of all mailboxes
  223.  * Accepts: mail stream
  224.  *        pattern to search
  225.  */
  226.  
  227. void map_find_all (MAILSTREAM *stream,char *pat)
  228. {
  229.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  230.   if (stream) {            /* have a mailbox stream open? */
  231.                 /* begin with a host specification? */
  232.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  233.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  234.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  235.       strcpy (tmp,pat);        /* copy host name */
  236.       tmp[patx-pat] = '\0';    /* tie off prefix */
  237.       LOCAL->prefix = cpystr (tmp);
  238.     }
  239.     else patx = pat;        /* use entire specification */
  240.                 /* this is optional, so no complaint if fail */
  241.     if (LOCAL && LOCAL->use_find &&
  242.     !strcmp (imap_sendq1 (stream,"FIND ALL.MAILBOXES",patx)->key,"BAD")) {
  243.       map_find (stream,pat);    /* perhaps older server */
  244.                 /* always include INBOX for consistency */
  245.       sprintf (tmp,"%sINBOX",LOCAL->prefix);
  246.       if (pmatch (pat,"INBOX")) mm_mailbox (tmp);
  247.     }
  248.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  249.   }
  250. }
  251.  
  252.  
  253. /* Mail Access Protocol find list of all bboards
  254.  * Accepts: mail stream
  255.  *        pattern to search
  256.  */
  257.  
  258. void map_find_all_bboards (MAILSTREAM *stream,char *pat)
  259. {
  260.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  261.   if (stream) {            /* have a mailbox stream open? */
  262.                 /* begin with a host specification? */
  263.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  264.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  265.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  266.       strcpy (tmp,pat);        /* copy host name */
  267.       tmp[patx-pat] = '\0';    /* tie off prefix */
  268.       LOCAL->prefix = cpystr (tmp);
  269.     }
  270.     else patx = pat;        /* use entire specification */
  271.                 /* this is optional, so no complaint if fail */
  272.     if (LOCAL && LOCAL->use_find &&
  273.     !strcmp (imap_sendq1 (stream,"FIND ALL.BBOARDS",patx)->key,"BAD"))
  274.       map_find_bboards (stream,pat);
  275.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  276.   }
  277. }
  278.  
  279. /* Mail Access Protocol subscribe to mailbox
  280.  * Accepts: mail stream
  281.  *        mailbox to add to subscription list
  282.  * Returns: T on success, NIL on failure
  283.  */
  284.  
  285. long map_subscribe (MAILSTREAM *stream,char *mailbox)
  286. {
  287.   return map_manage (stream,mailbox,"Subscribe Mailbox",NIL);
  288. }
  289.  
  290.  
  291. /* Mail access protocol unsubscribe to mailbox
  292.  * Accepts: mail stream
  293.  *        mailbox to delete from manage list
  294.  * Returns: T on success, NIL on failure
  295.  */
  296.  
  297. long map_unsubscribe (MAILSTREAM *stream,char *mailbox)
  298. {
  299.   return map_manage (stream,mailbox,"Unsubscribe Mailbox",NIL);
  300. }
  301.  
  302.  
  303. /* Mail Access Protocol subscribe to bboard
  304.  * Accepts: mail stream
  305.  *        mailbox to add to manage list
  306.  * Returns: T on success, NIL on failure
  307.  */
  308.  
  309. long map_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  310. {
  311.   return map_manage (stream,mailbox,"Subscribe BBoard",NIL);
  312. }
  313.  
  314.  
  315. /* Mail access protocol unsubscribe to bboard
  316.  * Accepts: mail stream
  317.  *        mailbox to delete from manage list
  318.  * Returns: T on success, NIL on failure
  319.  */
  320.  
  321. long map_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  322. {
  323.   return map_manage (stream,mailbox,"Unsubscribe BBoard",NIL);
  324. }
  325.  
  326. /* Mail Access Protocol create mailbox
  327.  * Accepts: mail stream
  328.  *        mailbox name to create
  329.  * Returns: T on success, NIL on failure
  330.  */
  331.  
  332. long map_create (MAILSTREAM *stream,char *mailbox)
  333. {
  334.   return map_manage (stream,mailbox,"Create",NIL);
  335. }
  336.  
  337.  
  338. /* Mail Access Protocol delete mailbox
  339.  * Accepts: mail stream
  340.  *        mailbox name to delete
  341.  * Returns: T on success, NIL on failure
  342.  */
  343.  
  344. long map_delete (MAILSTREAM *stream,char *mailbox)
  345. {
  346.   return map_manage (stream,mailbox,"Delete",NIL);
  347. }
  348.  
  349.  
  350. /* Mail Access Protocol rename mailbox
  351.  * Accepts: mail stream
  352.  *        old mailbox name
  353.  *        new mailbox name
  354.  * Returns: T on success, NIL on failure
  355.  */
  356.  
  357. long map_rename (MAILSTREAM *stream,char *old,char *new)
  358. {
  359.   return map_manage (stream,old,"Rename",new);
  360. }
  361.  
  362. /* Mail Access Protocol manage a mailbox
  363.  * Accepts: mail stream
  364.  *        mailbox to manipulate
  365.  *        command to execute
  366.  *        optional second argument
  367.  * Returns: T on success, NIL on failure
  368.  */
  369.  
  370. long map_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
  371. {
  372.   long ret = NIL;
  373.                 /* make sure useful stream given */
  374.   MAILSTREAM *st = (stream && LOCAL && LOCAL->tcpstream) ? stream :
  375.     mail_open (NIL,mailbox,OP_HALFOPEN);
  376.   if (st) {            /* do the operation if valid stream */
  377.     char *s,tmp[MAILTMPLEN];
  378.                 /* KLUDGE: nuke host name in second argument */
  379.     if (arg2 && (*arg2 == '{') && (s = strchr (arg2,'}'))) arg2 = s + 1;
  380.     if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
  381.       IMAPPARSEDREPLY *reply = imap_sendq2 (st,command,tmp,arg2);
  382.       if (imap_OK (st,reply)) ret = T;
  383.       mm_log (reply->text, ret ? (long) NIL : ERROR);
  384.     }
  385.                 /* toss out temporary stream */
  386.     if (st != stream) mail_close (st);
  387.   }
  388.   else mm_log ("Can't access server",ERROR);
  389.   return ret;
  390. }
  391.  
  392. /* Mail Access Protocol open
  393.  * Accepts: stream to open
  394.  * Returns: stream to use on success, NIL on failure
  395.  */
  396.  
  397. MAILSTREAM *map_open (MAILSTREAM *stream)
  398. {
  399.   long i,j;
  400.   char c[2],usrnam[MAILTMPLEN],pwd[MAILTMPLEN],tmp[MAILTMPLEN];
  401.   NETMBX mb;
  402.   char *s;
  403.   void *tstream;
  404.   IMAPPARSEDREPLY *reply = NIL;
  405.                 /* return prototype for OP_PROTOTYPE call */
  406.   if (!stream) return &imapproto;
  407.   mail_valid_net_parse (stream->mailbox,&mb);
  408.   if (LOCAL) {            /* if stream opened earlier by us */
  409.     if (strcmp (ucase (strcpy (tmp,mb.host)),
  410.         ucase (strcpy (pwd,imap_host (stream))))) {
  411.                 /* if hosts are different punt it */
  412.       sprintf (tmp,"Closing connection to %s",imap_host (stream));
  413.       if (!stream->silent) mm_log (tmp,(long) NIL);
  414.       map_close (stream);
  415.     }    
  416.     else {            /* else recycle if still alive */
  417.       i = stream->silent;    /* temporarily mark silent */
  418.       stream->silent = T;    /* don't give mm_exists() events */
  419.       j = map_ping (stream);    /* learn if stream still alive */
  420.       stream->silent = i;    /* restore prior state */
  421.       if (j) {            /* was stream still alive? */
  422.     sprintf (tmp,"Reusing connection to %s",mb.host);
  423.     if (!stream->silent) mm_log (tmp,(long) NIL);
  424.     map_gc (stream,GC_TEXTS);
  425.       }
  426.       else map_close (stream);
  427.     }
  428.     mail_free_cache (stream);
  429.   }
  430.                 /* in case /debug switch given */
  431.   if (mb.dbgflag) stream->debug = T;
  432.  
  433.   if (!LOCAL) {            /* open new connection if no recycle */
  434.     stream->local = fs_get (sizeof (IMAPLOCAL));
  435.     LOCAL->reply.line = LOCAL->reply.tag = LOCAL->reply.key =
  436.       LOCAL->reply.text = LOCAL->prefix = LOCAL->user = NIL;
  437.     LOCAL->use_body = LOCAL->use_find = LOCAL->use_bboard =
  438.       LOCAL->use_purge = T;    /* assume maximal server */
  439.     LOCAL->tcpstream = NIL;    /* no TCP stream yet */
  440.                 /* try authenticated open */
  441.     if (tstream = (stream->anonymous || mb.anoflag || mb.port) ? NIL :
  442.     tcp_aopen (mb.host,"/etc/rimapd",usrnam)) {
  443.                 /* if success, see if reasonable banner */
  444.       if (tcp_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
  445.     i = 0;            /* copy to buffer */
  446.     do tmp[i++] = *c;
  447.     while (tcp_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
  448.            (*c != '\012') && (i < (MAILTMPLEN-1)));
  449.     tmp[i] = '\0';        /* tie off */
  450.                 /* snarfed a valid greeting? */
  451.     if ((*c == '\015') && tcp_getbuffer (tstream,(long) 1,c) &&
  452.         (*c == '\012') &&
  453.         !strcmp((reply=imap_parse_reply(stream,s=cpystr(tmp)))->tag,"*")) {
  454.                 /* parse as IMAP */
  455.       imap_parse_unsolicited (stream,reply);
  456.                 /* set local stream if authenticated */
  457.       if (!strcmp (reply->key,"PREAUTH")) LOCAL->tcpstream = tstream;
  458.     }
  459.       }
  460.                 /* otherwise clean up */
  461.       if (tstream && !LOCAL->tcpstream) tcp_close (tstream);
  462.     }
  463.                 /* set up host with port override */
  464.     if (mb.port || map_port) sprintf (s = tmp,"%s:%ld",mb.host,
  465.                       mb.port ? mb.port : map_port);
  466.     else s = mb.host;        /* simple host name */
  467.     if (!LOCAL->tcpstream &&    /* try to open ordinary connection */
  468.     (LOCAL->tcpstream = tcp_open (s,"imap",IMAPTCPPORT)) &&
  469.     (!imap_OK (stream,reply = imap_reply (stream,NIL)))) {
  470.       mm_log (reply->text,ERROR);
  471.       map_close (stream);    /* failed, clean up */
  472.     }
  473.  
  474.     if (LOCAL && LOCAL->tcpstream && !strcmp (reply->key,"OK")) {
  475.                 /* only so many tries to login */
  476.       for (i = 0; LOCAL && LOCAL->tcpstream && (i < map_maxlogintrials); ++i) {
  477.     *pwd = 0;        /* get password */
  478.                 /* if caller wanted anonymous access */
  479.     if ((mb.anoflag || stream->anonymous) && !i) {
  480.       strcpy (usrnam,"anonymous");
  481.       strcpy (pwd,tcp_localhost (LOCAL->tcpstream));
  482.     }
  483.     else mm_login (map_loginfullname ? stream->mailbox :
  484.                tcp_host (LOCAL->tcpstream),usrnam,pwd,i);
  485.                 /* abort if he refuses to give a password */
  486.     if (*pwd == '\0') i = map_maxlogintrials;
  487.     else {            /* send "LOGIN usrnam pwd" */
  488.       if (imap_OK (stream,reply = imap_sendq2(stream,"LOGIN",usrnam,pwd))){
  489.         stream->anonymous = strcmp (usrnam,"anonymous") ? NIL : T;
  490.         break;        /* success */
  491.       }
  492.                 /* output failure and try again */
  493.       mm_log (reply->text,WARN);
  494.                 /* give up now if connection died */
  495.       if (!strcmp (reply->key,"BYE")) i = map_maxlogintrials;
  496.     }
  497.       }
  498.                 /* give up if too many failures */
  499.       if (i >=  map_maxlogintrials) {
  500.     mm_log (*pwd ? "Too many login failures":"Login aborted",ERROR);
  501.     map_close (stream);
  502.       }
  503.     }
  504.                 /* failed utterly to open */
  505.     if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  506.   }
  507.  
  508.   if (LOCAL) {            /* have a connection now??? */
  509.                 /* note user name */
  510.     LOCAL->user = cpystr (usrnam);
  511.     stream->sequence++;        /* bump sequence number */
  512.     if (stream->halfopen ||    /* send "SELECT/EXAMINE/BBOARD mailbox" */
  513.     !imap_OK (stream,reply = imap_sendq1 (stream,mb.bbdflag ? "BBOARD" :
  514.                           (stream->rdonly ? "EXAMINE":
  515.                            "SELECT"),mb.mailbox))) {
  516.                 /* want die on error and not halfopen? */
  517.       if (map_closeonerror && !stream->halfopen) {
  518.     if (LOCAL && LOCAL->tcpstream) map_close (stream);
  519.     return NIL;        /* return open failure */
  520.       }
  521.                 /* half-open the stream */
  522.       fs_give ((void **) &stream->mailbox);
  523.       sprintf (tmp,"{%s}<no_mailbox>",imap_host (stream));
  524.       stream->mailbox = cpystr (tmp);
  525.       if (!stream->halfopen) {    /* output error message if didn't ask for it */
  526.     mm_log (reply->text,ERROR);
  527.     stream->halfopen = T;
  528.       }
  529.                 /* make sure dummy message counts */
  530.       mail_exists (stream,(long) 0);
  531.       mail_recent (stream,(long) 0);
  532.     }
  533.     else {            /* update mailbox name */
  534.       fs_give ((void **) &stream->mailbox);
  535.       sprintf (tmp,"%s{%s}%s",mb.bbdflag ? "*" : "",
  536.            imap_host (stream),mb.mailbox);
  537.       stream->mailbox = cpystr (tmp);
  538.       reply->text[11] = '\0';    /* note if server said it was readonly */
  539.       stream->rdonly = !strcmp (ucase (reply->text),"[READ-ONLY]");
  540.     }
  541.     if (!(stream->nmsgs || stream->silent || stream->halfopen))
  542.       mm_log ("Mailbox is empty",(long) NIL);
  543.   }
  544.                 /* give up if nuked during startup */
  545.   if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  546.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  547. }
  548.  
  549. /* Mail Access Protocol close
  550.  * Accepts: MAIL stream
  551.  */
  552.  
  553. void map_close (MAILSTREAM *stream)
  554. {
  555.   IMAPPARSEDREPLY *reply;
  556.   if (stream && LOCAL) {    /* send "LOGOUT" */
  557.     if (LOCAL->tcpstream &&
  558.     !imap_OK (stream,reply = imap_send0 (stream,"LOGOUT")))
  559.       mm_log (reply->text,WARN);
  560.                 /* close TCP connection if still open */
  561.     if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  562.     LOCAL->tcpstream = NIL;
  563.                 /* free up memory */
  564.     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  565.     if (LOCAL->user) fs_give ((void **) &LOCAL->user);
  566.     map_gc (stream,GC_TEXTS);    /* nuke the cached strings */
  567.                 /* nuke the local data */
  568.     fs_give ((void **) &stream->local);
  569.   }
  570. }
  571.  
  572.  
  573. /* Mail Access Protocol fetch fast information
  574.  * Accepts: MAIL stream
  575.  *        sequence
  576.  *
  577.  * Generally, map_fetchstructure is preferred
  578.  */
  579.  
  580. void map_fetchfast (MAILSTREAM *stream,char *sequence)
  581. {                /* send "FETCH sequence FAST" */
  582.   IMAPPARSEDREPLY *reply;
  583.   char *s = (strlen (sequence) > 1024) ? strchr (sequence+1024,',') : NIL;
  584.   if (s) *s++ = '\0';
  585.   if (!imap_OK (stream,reply = imap_send2 (stream,"FETCH",sequence,"FAST")))
  586.     mm_log (reply->text,ERROR);
  587.   if (s) map_fetchfast (stream,s);
  588. }
  589.  
  590.  
  591. /* Mail Access Protocol fetch flags
  592.  * Accepts: MAIL stream
  593.  *        sequence
  594.  */
  595.  
  596. void map_fetchflags (MAILSTREAM *stream,char *sequence)
  597. {                /* send "FETCH sequence FLAGS" */
  598.   IMAPPARSEDREPLY *reply;
  599.   char *s = (strlen (sequence) > 1024) ? strchr (sequence+1024,',') : NIL;
  600.   if (s) *s++ = '\0';
  601.   if (!imap_OK (stream,reply = imap_send2 (stream,"FETCH",sequence,"FLAGS")))
  602.     mm_log (reply->text,ERROR);
  603.   if (s) map_fetchflags (stream,s);
  604. }
  605.  
  606. /* Mail Access Protocol fetch structure
  607.  * Accepts: MAIL stream
  608.  *        message # to fetch
  609.  *        pointer to return body
  610.  * Returns: envelope of this message, body returned in body value
  611.  *
  612.  * Fetches the "fast" information as well
  613.  */
  614.  
  615. ENVELOPE *map_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  616. {
  617.   long i = msgno;
  618.   long j = min (msgno + map_lookahead - 1,stream->nmsgs);
  619.   char seq[20];
  620.   LONGCACHE *lelt;
  621.   ENVELOPE **env;
  622.   BODY **b;
  623.   IMAPPARSEDREPLY *reply;
  624.   if (stream->scache) {        /* short cache */
  625.     if (msgno != stream->msgno){/* flush old poop if a different message */
  626.       mail_free_envelope (&stream->env);
  627.       mail_free_body (&stream->body);
  628.     }
  629.     stream->msgno = msgno;
  630.     env = &stream->env;        /* get pointers to envelope and body */
  631.     b = &stream->body;
  632.     sprintf (seq,"%ld",msgno);    /* never lookahead with a short cache */
  633.   }
  634.   else {            /* long cache */
  635.     lelt = mail_lelt (stream,msgno);
  636.     env = &lelt->env;        /* get pointers to envelope and body */
  637.     b = &lelt->body;
  638.     if (msgno != stream->nmsgs)    /* determine lookahead range */
  639.       while (i < j && !mail_lelt (stream,i+1)->env) i++;
  640.     sprintf (seq,"%ld:%ld",msgno,i);
  641.   }
  642.                 /* have the poop we need? */
  643.   if ((body && !*b && LOCAL->use_body) || !*env) {
  644.     mail_free_envelope (env);    /* flush old envelope and body */
  645.     mail_free_body (b);
  646.     if (!(body && LOCAL->use_body &&
  647.       (LOCAL->use_body =
  648.        (strcmp((reply = imap_send2(stream,"FETCH",seq,"FULL"))->key,"BAD")?
  649.         T : NIL)))) reply = imap_send2 (stream,"FETCH",seq,"ALL");
  650.     if (!imap_OK (stream,reply)) {
  651.       mm_log (reply->text,ERROR);
  652.       return NIL;
  653.     }
  654.   }
  655.   if (body) *body = *b;        /* return the body */
  656.   return *env;            /* return the envelope */
  657. }
  658.  
  659. /* Mail Access Protocol fetch message header
  660.  * Accepts: MAIL stream
  661.  *        message # to fetch
  662.  * Returns: message header in RFC822 format
  663.  */
  664.  
  665. char *map_fetchheader (MAILSTREAM *stream,long msgno)
  666. {
  667.   char seq[20];
  668.   IMAPPARSEDREPLY *reply;
  669.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  670.   if (!elt->data1) {        /* not if already cached */
  671.     sprintf (seq,"%ld",msgno);
  672.     if (!imap_OK (stream,    /* send "FETCH msgno RFC822.HEADER" */
  673.           reply = imap_send2 (stream,"FETCH",seq,"RFC822.HEADER")))
  674.       mm_log (reply->text,ERROR);
  675.   }
  676.   return elt->data1 ? (char *) elt->data1 : "";
  677. }
  678.  
  679.  
  680. /* Mail Access Protocol fetch message text (body only)
  681.  * Accepts: MAIL stream
  682.  *        message # to fetch
  683.  * Returns: message text in RFC822 format
  684.  */
  685.  
  686. char *map_fetchtext (MAILSTREAM *stream,long msgno)
  687. {
  688.   char seq[20];
  689.   IMAPPARSEDREPLY *reply;
  690.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  691.   if (!elt->data2) {        /* send "FETCH msgno RFC822.TEXT" */
  692.     sprintf (seq,"%ld",msgno);
  693.     if (!imap_OK (stream,reply = imap_send2(stream,"FETCH",seq,"RFC822.TEXT")))
  694.       mm_log (reply->text,ERROR);
  695.   }
  696.   return elt->data2 ? (char *) elt->data2 : "";
  697. }
  698.  
  699. /* Mail Access Protocol fetch message body as a structure
  700.  * Accepts: Mail stream
  701.  *        message # to fetch
  702.  *        section specifier
  703.  *        pointer to length
  704.  * Returns: pointer to section of message body
  705.  */
  706.  
  707. char *map_fetchbody (MAILSTREAM *stream,long m,char *sec,unsigned long *len)
  708. {
  709.   BODY *b;
  710.   PART *pt;
  711.   char *s = sec;
  712.   char **ss;
  713.   unsigned long i;
  714.   char seq[20],sect[MAILTMPLEN];
  715.   IMAPPARSEDREPLY *reply;
  716.   *len = 0;            /* in case failure */
  717.                 /* make sure have a body */
  718.   if (!(LOCAL->use_body && map_fetchstructure (stream,m,&b) && b)) {
  719.                 /* bodies not supported, wanted section 1? */
  720.     if (strcmp (sec,"1")) return NIL;
  721.                 /* yes, return text */
  722.     *len = strlen (s = map_fetchtext (stream,m));
  723.     return s;
  724.   }
  725.   if (!(s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  726.   do {                /* until find desired body part */
  727.                 /* multipart content? */
  728.     if (b->type == TYPEMULTIPART) {
  729.       pt = b->contents.part;    /* yes, find desired part */
  730.       while (--i && (pt = pt->next));
  731.       if (!pt) return NIL;    /* bad specifier */
  732.                 /* note new body, check valid nesting */
  733.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  734.     }
  735.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  736.                 /* need to go down further? */
  737.     if (i = *s) switch (b->type) {
  738.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  739.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  740.     case TYPEMULTIPART:        /* multipart, get next section */
  741.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  742.     default:            /* bogus subpart specification */
  743.       return NIL;
  744.     }
  745.   } while (i);
  746.  
  747.                 /* lose if body bogus */
  748.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  749.   switch (b->type) {        /* decide where the data is based on type */
  750.   case TYPEMESSAGE:        /* encapsulated message */
  751.     ss = &b->contents.msg.text;
  752.     break;
  753.   case TYPETEXT:        /* textual data */
  754.     ss = (char **) &b->contents.text;
  755.     break;
  756.   default:            /* otherwise assume it is binary */
  757.     ss = (char **) &b->contents.binary;
  758.     break;
  759.   }
  760.   if (!*ss) {            /* fetch data if don't have it yet */
  761.     sprintf (seq,"%ld",m);
  762.     sprintf (sect,"BODY[%s]",sec);
  763.     if (!imap_OK (stream,reply = imap_send2 (stream,"FETCH",seq,sect)))
  764.       mm_log (reply->text,ERROR);
  765.   }
  766.                 /* return data size if have data */
  767.   if (s = *ss) *len = b->size.bytes;
  768.   return s;
  769. }
  770.  
  771. /* Mail Access Protocol set flag
  772.  * Accepts: MAIL stream
  773.  *        sequence
  774.  *        flag(s)
  775.  */
  776.  
  777. void map_setflag (MAILSTREAM *stream,char *seq,char *flag)
  778. {
  779.   IMAPPARSEDREPLY *reply;
  780.   char *s = (strlen (seq) > 1024) ? strchr (seq+1024,',') : NIL;
  781.   if (s) *s++ = '\0';
  782.                 /* send "STORE seq +Flags flag" */
  783.   if (!imap_OK (stream,reply = imap_send2f (stream,"STORE",seq,"+Flags",flag)))
  784.     mm_log (reply->text,ERROR);
  785.   if (s) map_setflag (stream,s,flag);
  786. }
  787.  
  788.  
  789. /* Mail Access Protocol clear flag
  790.  * Accepts: MAIL stream
  791.  *        sequence
  792.  *        flag(s)
  793.  */
  794.  
  795. void map_clearflag (MAILSTREAM *stream,char *seq,char *flag)
  796. {
  797.   IMAPPARSEDREPLY *reply;
  798.   char *s = (strlen (seq) > 1024) ? strchr (seq+1024,',') : NIL;
  799.   if (s) *s++ = '\0';
  800.                 /* send "STORE seq -Flags flag" */
  801.   if (!imap_OK (stream,reply = imap_send2f (stream,"STORE",seq,"-Flags",flag)))
  802.     mm_log (reply->text,ERROR);
  803.   if (s) map_clearflag (stream,s,flag);
  804. }
  805.  
  806. /* Mail Access Protocol search for messages
  807.  * Accepts: MAIL stream
  808.  *        search criteria
  809.  */
  810.  
  811. void map_search (MAILSTREAM *stream,char *criteria)
  812. {
  813.   long i,j,k;
  814.   char *s;
  815.   IMAPPARSEDREPLY *reply;
  816.   MESSAGECACHE *elt;
  817.                 /* do the SEARCH */
  818.   if (imap_OK (stream,reply = imap_send1 (stream,"SEARCH",criteria))) {
  819.                 /* can never pre-fetch with a short cache */
  820.     if (!(k = map_prefetch) || stream->scache) return;
  821.     s = LOCAL->tmp;        /* build sequence in temporary buffer */
  822.     *s = '\0';            /* initially nothing */
  823.                 /* search through mailbox */
  824.     for (i = 1; k && (i <= stream->nmsgs); ++i) 
  825.                 /* for searched messages with no envelope */
  826.       if ((elt = mail_elt (stream,i)) && elt->searched &&
  827.       !mail_lelt (stream,i)->env) {
  828.                 /* prepend with comma if not first time */
  829.     if (LOCAL->tmp[0]) *s++ = ',';
  830.     sprintf (s,"%ld",j = i);/* output message number */
  831.     k--;            /* count one up */
  832.     s += strlen (s);    /* point at end of string */
  833.                 /* search for possible end of range */
  834.     while (k && (i < stream->nmsgs) && (elt = mail_elt (stream,i+1)) &&
  835.            elt->searched && !mail_lelt (stream,i+1)->env) i++,k--;
  836.     if (i != j) {        /* if a range */
  837.       sprintf (s,":%ld",i);    /* output delimiter and end of range */
  838.       s += strlen (s);    /* point at end of string */
  839.     }
  840.       }
  841.     if (LOCAL->tmp[0]) {    /* anything to pre-fetch? */
  842.       s = cpystr (LOCAL->tmp);    /* yes, copy sequence */
  843.       if (!imap_OK (stream,reply = imap_send2 (stream,"FETCH",s,"ALL")))
  844.     mm_log (reply->text,ERROR);
  845.       fs_give ((void **) &s);    /* flush copy of sequence */
  846.     }
  847.   }
  848.   else mm_log (reply->text,ERROR);
  849. }
  850.  
  851. /* Mail Access Protocol ping mailbox
  852.  * Accepts: MAIL stream
  853.  * Returns: T if stream still alive, else NIL
  854.  */
  855.  
  856. long map_ping (MAILSTREAM *stream)
  857. {
  858.   return (LOCAL->tcpstream &&    /* send "NOOP" */
  859.       imap_OK (stream,imap_send0 (stream,"NOOP"))) ? T : NIL;
  860. }
  861.  
  862.  
  863. /* Mail Access Protocol check mailbox
  864.  * Accepts: MAIL stream
  865.  */
  866.  
  867. void map_check (MAILSTREAM *stream)
  868. {
  869.                 /* send "CHECK" */
  870.   IMAPPARSEDREPLY *reply = imap_send0 (stream,"CHECK");
  871.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  872. }
  873.  
  874.  
  875. /* Mail Access Protocol expunge mailbox
  876.  * Accepts: MAIL stream
  877.  */
  878.  
  879. void map_expunge (MAILSTREAM *stream)
  880. {
  881.                 /* send "EXPUNGE" */
  882.   IMAPPARSEDREPLY *reply = imap_send0 (stream,"EXPUNGE");
  883.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  884. }
  885.  
  886. /* Mail Access Protocol copy message(s)
  887.  * Accepts: MAIL stream
  888.  *        sequence
  889.  *        destination mailbox
  890.  * Returns: T if successful else NIL
  891.  */
  892.  
  893. long map_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  894. {
  895.   IMAPPARSEDREPLY *reply;
  896.   char *s = (strlen (sequence) > 1024) ? strchr (sequence+1024,',') : NIL;
  897.   if (s) *s++ = '\0';
  898.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  899.     mm_log ("Copy rejected: connection to remote IMAP server closed",ERROR);
  900.     return NIL;
  901.   }
  902.                 /* send "COPY sequence mailbox" */
  903.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",NIL,sequence,NIL,NIL,
  904.                       mailbox,NIL))) {
  905.     mm_log (reply->text,ERROR);
  906.     return NIL;
  907.   }
  908.   return s ? map_copy (stream,s,mailbox) : LONGT;
  909. }
  910.  
  911.  
  912. /* Mail Access Protocol move message(s)
  913.  * Accepts: MAIL stream
  914.  *        sequence
  915.  *        destination mailbox
  916.  * Returns: T if successful else NIL
  917.  */
  918.  
  919. long map_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  920. {
  921.   if (!map_copy (stream,sequence,mailbox)) return NIL;
  922.   map_setflag (stream,sequence,"\\Deleted");
  923.   return T;
  924. }
  925.  
  926. /* Mail Access Protocol append message string
  927.  * Accepts: mail stream
  928.  *        destination mailbox
  929.  *        stringstruct of message to append
  930.  * Returns: T on success, NIL on failure
  931.  */
  932.  
  933. long map_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  934.          STRING *msg)
  935. {
  936.   long ret = NIL;
  937.                 /* make sure useful stream given */
  938.   MAILSTREAM *st = (stream && LOCAL && LOCAL->tcpstream) ? stream :
  939.     mail_open (NIL,mailbox,OP_HALFOPEN);
  940.   if (st) {            /* do the operation if valid stream */
  941.     char tmp[MAILTMPLEN];
  942.                 /* try IMAP4 form */
  943.     if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
  944.       IMAPPARSEDREPLY *reply=imap_send(st,"APPEND",tmp,NIL,NIL,flags,date,msg);
  945.                 /* try IMAP2bis form if IMAP4 form fails */
  946.       if ((!strcmp (reply->key,"OK")) ||
  947.       ((!strcmp (reply->key,"BAD")) &&
  948.        imap_OK (st,reply=imap_send(st,"APPEND",tmp,NIL,NIL,NIL,NIL,msg))))
  949.     ret = T;
  950.       else mm_log (reply->text,ERROR);
  951.     }
  952.                 /* toss out temporary stream */
  953.     if (st != stream) mail_close (st);
  954.   }
  955.   else mm_log ("Can't access server for append",ERROR);
  956.   return ret;            /* return */
  957. }
  958.  
  959. /* Mail Access Protocol garbage collect stream
  960.  * Accepts: Mail stream
  961.  *        garbage collection flags
  962.  */
  963.  
  964. void map_gc (MAILSTREAM *stream,long gcflags)
  965. {
  966.   unsigned long i;
  967.   MESSAGECACHE *elt;
  968.   LONGCACHE *lelt;
  969.   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  970.                 /* make sure the cache is large enough */
  971.   (*mc) (stream,stream->nmsgs,CH_SIZE);
  972.   if (gcflags & GC_TEXTS) {    /* garbage collect texts? */
  973.     for (i = 1; i <= stream->nmsgs; ++i)
  974.       if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) {
  975.     if (elt->data1) fs_give ((void **) &elt->data1);
  976.     if (elt->data2) fs_give ((void **) &elt->data2);
  977.     if (!stream->scache) map_gc_body ((lelt = mail_lelt (stream,i))->body);
  978.       }
  979.     map_gc_body (stream->body);    /* free texts from short cache body */
  980.   }
  981.                 /* gc cache if requested and unlocked */
  982.   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
  983.     if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
  984.     (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
  985. }
  986.  
  987. /* Mail Access Protocol garbage collect body texts
  988.  * Accepts: body to GC
  989.  */
  990.  
  991. void map_gc_body (BODY *body)
  992. {
  993.   PART *part;
  994.   if (body) switch (body->type) {
  995.   case TYPETEXT:        /* unformatted text */
  996.     if (body->contents.text) fs_give ((void **) &body->contents.text);
  997.     break;
  998.   case TYPEMULTIPART:        /* multiple part */
  999.     if (part = body->contents.part) do map_gc_body (&part->body);
  1000.     while (part = part->next);
  1001.     break;
  1002.   case TYPEMESSAGE:        /* encapsulated message */
  1003.     map_gc_body (body->contents.msg.body);
  1004.     if (body->contents.msg.text)
  1005.       fs_give ((void **) &body->contents.msg.text);
  1006.     break;
  1007.   case TYPEAPPLICATION:        /* application data */
  1008.   case TYPEAUDIO:        /* audio */
  1009.   case TYPEIMAGE:        /* static image */
  1010.   case TYPEVIDEO:        /* video */
  1011.     if (body->contents.binary) fs_give (&body->contents.binary);
  1012.     break;
  1013.   default:
  1014.     break;
  1015.   }
  1016. }
  1017.  
  1018. /* Internal routines */
  1019.  
  1020.  
  1021. /* Mail Access Protocol return host name
  1022.  * Accepts: MAIL stream
  1023.  * Returns: host name
  1024.  */
  1025.  
  1026. char *imap_host (MAILSTREAM *stream)
  1027. {
  1028.                 /* return host name on stream if open */
  1029.   return (LOCAL && LOCAL->tcpstream) ? tcp_host (LOCAL->tcpstream) :
  1030.     "<closed stream>";
  1031. }
  1032.  
  1033.  
  1034. /* IMAP return port number
  1035.  * Accepts: MAIL stream
  1036.  * Returns: port number
  1037.  */
  1038.  
  1039. long imap_port (MAILSTREAM *stream)
  1040. {
  1041.                 /* return port number on stream if open */
  1042.   return (LOCAL && LOCAL->tcpstream) ? tcp_port (LOCAL->tcpstream) :
  1043.     0xffffffff;
  1044. }
  1045.  
  1046.  
  1047. /* Mail Access Protocol return logged-in user name
  1048.  * Accepts: MAIL stream
  1049.  * Returns: logged-in user name
  1050.  */
  1051.  
  1052. char *imap_user (MAILSTREAM *stream)
  1053. {
  1054.   return (LOCAL && LOCAL->user) ? LOCAL->user : NIL;
  1055. }
  1056.  
  1057. /* Mail Access Protocol send command
  1058.  * Accepts: MAIL stream
  1059.  *        command
  1060.  *        first argument (may be quoted)
  1061.  *        second argument (never quoted)
  1062.  *        third argument (never quoted)
  1063.  *        fourth argument (flags, may be parenthesized)
  1064.  *        fifth argument (may be quoted)
  1065.  *        sixth argument (always literal)
  1066.  * Returns: parsed reply
  1067.  */
  1068.  
  1069. IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,char *a1,char *a2,
  1070.                 char *a3,char *a4,char *a5,STRING *a6)
  1071. {
  1072.   char c,*t,*u,tag[7];
  1073.   char *s = LOCAL->tmp;
  1074.   unsigned long i;
  1075.   IMAPPARSEDREPLY *reply;
  1076.   if (!LOCAL->tcpstream) return imap_fake (stream,tag,"No-op dead stream");
  1077.   mail_lock (stream);        /* lock up the stream */
  1078.                   /* gensym a new tag */
  1079.   sprintf (tag,"A%05ld",stream->gensym++);
  1080.   sprintf (s,"%s %s",tag,cmd);    /* begin command */
  1081.   if (a1) {            /* only do this if first arg present */
  1082.     s += strlen (s);        /* append first argument */
  1083.     if (strpbrk (a1,"\012\015\"%{\\")) {
  1084.       sprintf (s," {%ld}",i = (long) strlen (a1));
  1085.                 /* output debugging telemetry */
  1086.       if (stream->debug) mm_dlog (LOCAL->tmp);
  1087.       strcat (s,"\015\012");    /* append newline */
  1088.                 /* send the command */
  1089.       if (reply = imap_soutr (stream,tag,LOCAL->tmp)) return reply;
  1090.       if (strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1091.     mail_unlock (stream);
  1092.     return reply;
  1093.       }
  1094.       strcpy (s = LOCAL->tmp,a1); /* restart buffer at this point */
  1095.     }
  1096.     else sprintf (s,strchr (a1,' ') ? " \"%s\"" : " %s",a1);
  1097.   }
  1098.  
  1099.   if (a2) {            /* only do this if second arg present */
  1100.     s += strlen (s);        /* prepare to append second argument */
  1101.     *s++ = ' ';            /* delimit with space */
  1102.                 /* embedded literal from SEARCH? */
  1103.     while (t = strstr (a2,"}\015\012")) {
  1104.                 /* yes, copy and note its start */
  1105.       for (u = NIL; a2 <= t; *s++ = *a2++) if (*a2 == '{') u = a2;
  1106.       *s = '\0';        /* tie off string */
  1107.       if (stream->debug) mm_dlog (LOCAL->tmp);
  1108.                 /* append newline */
  1109.       *s++ = *a2++; *s++ = *a2++; *s = '\0';
  1110.                 /* get size of literal */
  1111.       if (!(u && (i = strtol (u+1,&u,10)) && (u == t)))
  1112.     return imap_fake (stream,tag,"Program bug: bad literal");
  1113.                 /* send the command */
  1114.       if (reply = imap_soutr (stream,tag,LOCAL->tmp)) return reply;
  1115.       if (strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1116.     mail_unlock (stream);
  1117.     return reply;
  1118.       }
  1119.                 /* copy literal data */
  1120.       for (s = LOCAL->tmp; i--; *s++ = *a2++);
  1121.     }
  1122.     strcpy (s,a2);        /* note this one is NEVER quoted */
  1123.   }
  1124.   if (a3) {            /* only do this if third arg present */
  1125.     s += strlen (s);        /* append third argument */
  1126.     sprintf (s," %s",a3);    /* note this one is NEVER quoted */
  1127.   }
  1128.  
  1129.   if (a4) {            /* only do this if fourth arg present */
  1130.     s += strlen (s);        /* append fourth argument */
  1131.     sprintf (s,(*a4 == '(') ? " %s" : " (%s)",a4);
  1132.   }
  1133.   if (a5) {            /* only do this if fifth arg present */
  1134.     s += strlen (s);        /* tally what we have so far */
  1135.     if (strpbrk (a5,"\012\015\"()*%{\\")) {
  1136.       sprintf (s," {%ld}",i = (long) strlen (a5));
  1137.                 /* output debugging telemetry */
  1138.       if (stream->debug) mm_dlog (LOCAL->tmp);
  1139.       strcat (s,"\015\012");    /* append newline */
  1140.                 /* send the command */
  1141.       if (reply = imap_soutr (stream,tag,LOCAL->tmp)) return reply;
  1142.       if (strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1143.     mail_unlock (stream);
  1144.     return reply;
  1145.       }
  1146.       strcpy (s=LOCAL->tmp,a5);    /* restart buffer at this point */
  1147.     }
  1148.     else sprintf (s,strchr (a5,' ') ? " \"%s\"" : " %s",a5);
  1149.   }
  1150.   if (a6) {            /* only do this if sixth arg present */
  1151.     s += strlen (s);        /* append third argument */
  1152.     sprintf (s," {%ld}",i = SIZE (a6));
  1153.                 /* output debugging telemetry */
  1154.     if (stream->debug) mm_dlog (LOCAL->tmp);
  1155.     strcat (s,"\015\012");    /* append newline */
  1156.                 /* send the command */
  1157.     if (reply = imap_soutr (stream,tag,LOCAL->tmp)) return reply;
  1158.     if (strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1159.       mail_unlock (stream);
  1160.       return reply;
  1161.     }
  1162.     while (i) {            /* dump the message */
  1163.       if (!tcp_sout (LOCAL->tcpstream,a6->curpos,a6->cursize)) {
  1164.     mail_unlock (stream);
  1165.     return imap_fake (stream,tag,"IMAP connection broken (command data)");
  1166.       }
  1167.       i -= a6->cursize;        /* note that we wrote out this much */
  1168.       a6->curpos += (a6->cursize - 1);
  1169.       a6->cursize = 1;
  1170.       SNX (a6);            /* advance to next buffer's worth */
  1171.     }
  1172.     s = LOCAL->tmp;        /* finish up command */
  1173.     *s = '\0';            /* tie off as empty */
  1174.   }
  1175.                 /* output debugging telemetry */
  1176.   if (stream->debug) mm_dlog (LOCAL->tmp);
  1177.   strcat (s,"\015\012");    /* append newline */
  1178.                 /* send the command */
  1179.   if (reply = imap_soutr (stream,tag,LOCAL->tmp)) return reply;
  1180.   reply = imap_reply (stream,tag);
  1181.   mail_unlock (stream);        /* unlock stream */
  1182.   return reply;            /* return reply to caller */
  1183. }
  1184.  
  1185. /* Mail Access Protocol send string as record
  1186.  * Accepts: MAIL stream
  1187.  *        string pointer
  1188.  * Returns: NIL if success else fake reply
  1189.  */
  1190.  
  1191. IMAPPARSEDREPLY *imap_soutr (MAILSTREAM *stream,char *tag,char *string)
  1192. {
  1193.   if (tcp_soutr (LOCAL->tcpstream,string)) return NIL;
  1194.   mail_unlock (stream);
  1195.   return imap_fake (stream,tag,"IMAP connection broken (command)");
  1196. }
  1197.  
  1198.  
  1199. /* Mail Access Protocol get reply
  1200.  * Accepts: MAIL stream
  1201.  *        tag to search or NIL if want a greeting
  1202.  * Returns: parsed reply, never NIL
  1203.  */
  1204.  
  1205. IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
  1206. {
  1207.   IMAPPARSEDREPLY *reply;
  1208.   while (LOCAL->tcpstream) {    /* parse reply from server */
  1209.     if (reply = imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream))) {
  1210.                 /* continuation ready? */
  1211.       if (!strcmp (reply->tag,"+")) return reply;
  1212.                 /* untagged data? */
  1213.       else if (!strcmp (reply->tag,"*")) {
  1214.     imap_parse_unsolicited (stream,reply);
  1215.     if (!tag) return reply;    /* return if just wanted greeting */
  1216.       }
  1217.       else {            /* tagged data */
  1218.     if (tag && !strcmp (tag,reply->tag)) return reply;
  1219.                 /* report bogon */
  1220.     sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
  1221.          reply->tag,reply->key,reply->text);
  1222.     mm_log (LOCAL->tmp,WARN);
  1223.       }
  1224.     }
  1225.   }
  1226.   return imap_fake (stream,tag,"IMAP connection broken (server response)");
  1227. }
  1228.  
  1229. /* Mail Access Protocol parse reply
  1230.  * Accepts: MAIL stream
  1231.  *        text of reply
  1232.  * Returns: parsed reply, or NIL if can't parse at least a tag and key
  1233.  */
  1234.  
  1235.  
  1236. IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
  1237. {
  1238.   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  1239.   if (!(LOCAL->reply.line = text)) {
  1240.                 /* NIL text means the stream died */
  1241.     tcp_close (LOCAL->tcpstream);
  1242.     LOCAL->tcpstream = NIL;
  1243.     return NIL;
  1244.   }
  1245.   if (stream->debug) mm_dlog (LOCAL->reply.line);
  1246.   LOCAL->reply.key = NIL;    /* init fields in case error */
  1247.   LOCAL->reply.text = NIL;
  1248.   if (!(LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," "))) {
  1249.     mm_log ("IMAP server sent a blank line",WARN);
  1250.     return NIL;
  1251.   }
  1252.                 /* non-continuation replies */
  1253.   if (strcmp (LOCAL->reply.tag,"+")) {
  1254.                 /* parse key */
  1255.     if (!(LOCAL->reply.key = (char *) strtok (NIL," "))) {
  1256.                 /* determine what is missing */
  1257.       sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
  1258.       mm_log (LOCAL->tmp,WARN);    /* pass up the barfage */
  1259.       return NIL;        /* can't parse this text */
  1260.     }
  1261.     ucase (LOCAL->reply.key);    /* make sure key is upper case */
  1262.                 /* get text as well, allow empty text */
  1263.     if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
  1264.       LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
  1265.   }
  1266.   else {            /* special handling of continuation */
  1267.     LOCAL->reply.key = "BAD";    /* so it barfs if not expecting continuation */
  1268.     if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
  1269.       LOCAL->reply.text = "Ready for more command";
  1270.   }
  1271.   return &LOCAL->reply;        /* return parsed reply */
  1272. }
  1273.  
  1274. /* Mail Access Protocol fake reply
  1275.  * Accepts: MAIL stream
  1276.  *        tag
  1277.  *        text of fake reply
  1278.  * Returns: parsed reply
  1279.  */
  1280.  
  1281. IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
  1282. {
  1283.   mm_notify (stream,text,BYE);    /* send bye alert */
  1284.   if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  1285.   LOCAL->tcpstream = NIL;    /* farewell, dear TCP stream... */
  1286.                 /* build fake reply string */
  1287.   sprintf (LOCAL->tmp,"%s NO [CLOSED] %s",tag ? tag : "*",text);
  1288.                 /* parse and return it */
  1289.   return imap_parse_reply (stream,cpystr (LOCAL->tmp));
  1290. }
  1291.  
  1292.  
  1293. /* Mail Access Protocol check for OK response in tagged reply
  1294.  * Accepts: MAIL stream
  1295.  *        parsed reply
  1296.  * Returns: T if OK else NIL
  1297.  */
  1298.  
  1299. long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1300. {
  1301.                 /* OK - operation succeeded */
  1302.   if (!strcmp (reply->key,"OK") ||
  1303.       (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH")))
  1304.     return T;
  1305.                 /* NO - operation failed */
  1306.   else if (strcmp (reply->key,"NO")) {
  1307.                 /* BAD - operation rejected */
  1308.     if (!strcmp (reply->key,"BAD"))
  1309.       sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
  1310.     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
  1311.           reply->key,reply->text);
  1312.     mm_log (LOCAL->tmp,WARN);    /* log the sucker */
  1313.   }
  1314.   return NIL;
  1315. }
  1316.  
  1317. /* Mail Access Protocol parse and act upon unsolicited reply
  1318.  * Accepts: MAIL stream
  1319.  *        parsed reply
  1320.  */
  1321.  
  1322. void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1323. {
  1324.   long msgno;
  1325.   char *s;
  1326.   char *keyptr,*txtptr;
  1327.                 /* see if key is a number */
  1328.   msgno = strtol (reply->key,&s,10);
  1329.   if (*s) {            /* if non-numeric */
  1330.     if (!strcmp (reply->key,"FLAGS")) imap_parse_flaglst (stream,reply);
  1331.     else if (!strcmp (reply->key,"SEARCH")) imap_searched (stream,reply->text);
  1332.     else if (!strcmp (reply->key,"MAILBOX")) {
  1333.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1334.       mm_mailbox (LOCAL->tmp);
  1335.     }
  1336.     else if (!strcmp (reply->key,"BBOARD")) {
  1337.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1338.       mm_bboard (LOCAL->tmp);
  1339.     }
  1340.     else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
  1341.                 /* note if server said it was readonly */
  1342.       strncpy (LOCAL->tmp,reply->text,11);
  1343.       LOCAL->tmp[11] = '\0';    /* tie off text */
  1344.       if (!strcmp (ucase (LOCAL->tmp),"[READ-ONLY]")) stream->rdonly = T;
  1345.       mm_notify (stream,reply->text,(long) NIL);
  1346.     }
  1347.     else if (!strcmp (reply->key,"NO")) {
  1348.       if (!stream->silent) mm_notify (stream,reply->text,WARN);
  1349.     }
  1350.     else if (!strcmp (reply->key,"BYE")) {
  1351.       if (!stream->silent) mm_notify (stream,reply->text,BYE);
  1352.     }
  1353.     else if (!strcmp (reply->key,"BAD")) mm_notify (stream,reply->text,ERROR);
  1354.     else {
  1355.       sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
  1356.       mm_log (LOCAL->tmp,WARN);
  1357.     }
  1358.   }
  1359.  
  1360.   else {            /* if numeric, a keyword follows */
  1361.                 /* deposit null at end of keyword */
  1362.     keyptr = ucase ((char *) strtok (reply->text," "));
  1363.                 /* and locate the text after it */
  1364.     txtptr = (char *) strtok (NIL,"\n");
  1365.                 /* now take the action */
  1366.                 /* change in size of mailbox */
  1367.     if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno);
  1368.     else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
  1369.     else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
  1370.     else if (!strcmp (keyptr,"FETCH"))
  1371.       imap_parse_data (stream,msgno,txtptr,reply);
  1372.                 /* obsolete alias for FETCH */
  1373.     else if (!strcmp (keyptr,"STORE"))
  1374.       imap_parse_data (stream,msgno,txtptr,reply);
  1375.                 /* obsolete response to COPY */
  1376.     else if (strcmp (keyptr,"COPY")) {
  1377.       sprintf (LOCAL->tmp,"Unknown message data: %ld %.80s",msgno,keyptr);
  1378.       mm_log (LOCAL->tmp,WARN);
  1379.     }
  1380.   }
  1381. }
  1382.  
  1383. /* Mail Access Protocol parse flag list
  1384.  * Accepts: MAIL stream
  1385.  *        parsed reply
  1386.  *
  1387.  *  The reply->line is yanked out of the parsed reply and stored on
  1388.  * stream->flagstring.  This is the original fs_get'd reply string, and
  1389.  * has all the flagstrings embedded in it.
  1390.  */
  1391.  
  1392. void imap_parse_flaglst (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1393. {
  1394.   char *text = reply->text;
  1395.   char *flag;
  1396.   long i;
  1397.                 /* flush old flagstring and flags if any */
  1398.   if (stream->flagstring) fs_give ((void **) &stream->flagstring);
  1399.   for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
  1400.                 /* remember this new one */
  1401.   stream->flagstring = reply->line;
  1402.   reply->line = NIL;
  1403.   ++text;            /* skip past open parenthesis */
  1404.                 /* get first flag if any */
  1405.   if (flag = (char *) strtok (text," )")) {
  1406.     i = 0;            /* init flag index */
  1407.                 /* add all user flags */
  1408.     do if (*flag != '\\') stream->user_flags[i++] = flag;
  1409.       while (flag = (char *) strtok (NIL," )"));
  1410.   }
  1411. }
  1412.  
  1413.  
  1414. /* Mail Access Protocol messages have been searched out
  1415.  * Accepts: MAIL stream
  1416.  *        list of message numbers
  1417.  *
  1418.  * Calls external "mail_searched" function to notify main program
  1419.  */
  1420.  
  1421. void imap_searched (MAILSTREAM *stream,char *text)
  1422. {
  1423.                 /* only do something if have text */
  1424.   if (text && (text = (char *) strtok (text," "))) 
  1425.     for (; text; text = (char *) strtok (NIL," "))
  1426.       mail_searched (stream,atol (text));
  1427. }
  1428.  
  1429. /* Mail Access Protocol message has been expunged
  1430.  * Accepts: MAIL stream
  1431.  *        message number
  1432.  *
  1433.  * Calls external "mail_searched" function to notify main program
  1434.  */
  1435.  
  1436. void imap_expunged (MAILSTREAM *stream,long msgno)
  1437. {
  1438.   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1439.   MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
  1440.   if (elt) {
  1441.     if (elt->data1) fs_give ((void **) &elt->data1);
  1442.     if (elt->data2) fs_give ((void **) &elt->data2);
  1443.   }
  1444.   mail_expunged (stream,msgno);    /* notify upper level */
  1445. }
  1446.  
  1447.  
  1448. /* Mail Access Protocol parse data
  1449.  * Accepts: MAIL stream
  1450.  *        message #
  1451.  *        text to parse
  1452.  *        parsed reply
  1453.  *
  1454.  *  This code should probably be made a bit more paranoid about malformed
  1455.  * S-expressions.
  1456.  */
  1457.  
  1458. void imap_parse_data (MAILSTREAM *stream,long msgno,char *text,
  1459.               IMAPPARSEDREPLY *reply)
  1460. {
  1461.   char *prop;
  1462.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1463.   ++text;            /* skip past open parenthesis */
  1464.                 /* parse Lisp-form property list */
  1465.   while (prop = (char *) strtok (text," )")) {
  1466.                 /* point at value */
  1467.     text = (char *) strtok (NIL,"\n");
  1468.                 /* parse the property and its value */
  1469.     imap_parse_prop (stream,elt,ucase (prop),&text,reply);
  1470.   }
  1471. }
  1472.  
  1473. /* Mail Access Protocol parse property
  1474.  * Accepts: MAIL stream
  1475.  *        cache item
  1476.  *        property name
  1477.  *        property value text pointer
  1478.  *        parsed reply
  1479.  */
  1480.  
  1481. void imap_parse_prop (MAILSTREAM *stream,MESSAGECACHE *elt,char *prop,
  1482.               char **txtptr,IMAPPARSEDREPLY *reply)
  1483. {
  1484.   char *s;
  1485.   ENVELOPE **env;
  1486.   BODY **body;
  1487.   if (!strcmp (prop,"ENVELOPE")) {
  1488.     if (stream->scache) {    /* short cache, flush old stuff */
  1489.       mail_free_envelope (&stream->env);
  1490.       mail_free_body (&stream->body);
  1491.       stream->msgno =elt->msgno;/* set new current message number */
  1492.       env = &stream->env;    /* get pointer to envelope */
  1493.     }
  1494.     else env = &mail_lelt (stream,elt->msgno)->env;
  1495.     imap_parse_envelope (stream,env,txtptr,reply);
  1496.   }
  1497.   else if (!strcmp (prop,"FLAGS")) imap_parse_flags (stream,elt,txtptr);
  1498.   else if (!strcmp (prop,"INTERNALDATE")) {
  1499.     if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1500.       if (!mail_parse_date (elt,s)) {
  1501.     sprintf (LOCAL->tmp,"Bogus date: %.80s",s);
  1502.     mm_log (LOCAL->tmp,WARN);
  1503.       }
  1504.       fs_give ((void **) &s);
  1505.     }
  1506.   }
  1507.   else if (!strcmp (prop,"RFC822.HEADER")) {
  1508.     if (elt->data1) fs_give ((void **) &elt->data1);
  1509.     elt->data1 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1510.                             elt->msgno);
  1511.   }
  1512.  
  1513.   else if (!strcmp (prop,"RFC822.SIZE"))
  1514.     elt->rfc822_size = imap_parse_number (stream,txtptr);
  1515.   else if (!strcmp (prop,"RFC822.TEXT")) {
  1516.     if (elt->data2) fs_give ((void **) &elt->data2);
  1517.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1518.                             elt->msgno);
  1519.   }
  1520.   else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' &&
  1521.        prop[3] == 'Y') {
  1522.     s = cpystr (prop+4);    /* copy segment specifier */
  1523.     if (stream->scache) {    /* short cache, flush old stuff */
  1524.       if (elt->msgno != stream->msgno) {
  1525.                 /* losing real bad here */
  1526.     mail_free_envelope (&stream->env);
  1527.     mail_free_body (&stream->body);
  1528.     sprintf (LOCAL->tmp,"Body received for %ld when current is %ld",
  1529.          elt->msgno,stream->msgno);
  1530.     mm_log (LOCAL->tmp,WARN);
  1531.     stream->msgno = elt->msgno;
  1532.       }
  1533.       body = &stream->body;    /* get pointer to body */
  1534.     }
  1535.     else body = &mail_lelt (stream,elt->msgno)->body;
  1536.     imap_parse_body (stream,elt->msgno,body,s,txtptr,reply);
  1537.     fs_give ((void **) &s);
  1538.   }
  1539.                 /* this shouldn't happen with our client */
  1540.   else if (!strcmp (prop,"RFC822")) {
  1541.     if (elt->data2) fs_give ((void **) &elt->data2);
  1542.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1543.                             elt->msgno);
  1544.   }
  1545.   else {
  1546.     sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
  1547.     mm_log (LOCAL->tmp,WARN);
  1548.   }
  1549. }
  1550.  
  1551. /* Mail Access Protocol parse envelope
  1552.  * Accepts: MAIL stream
  1553.  *        pointer to envelope pointer
  1554.  *        current text pointer
  1555.  *        parsed reply
  1556.  *
  1557.  * Updates text pointer
  1558.  */
  1559.  
  1560. void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,char **txtptr,
  1561.               IMAPPARSEDREPLY *reply)
  1562. {
  1563.   char c = *((*txtptr)++);    /* grab first character */
  1564.                 /* ignore leading spaces */
  1565.   while (c == ' ') c = *((*txtptr)++);
  1566.                 /* free any old envelope */
  1567.   if (*env) mail_free_envelope (env);
  1568.   switch (c) {            /* dispatch on first character */
  1569.   case '(':            /* if envelope S-expression */
  1570.     *env = mail_newenvelope ();    /* parse the new envelope */
  1571.     (*env)->date = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1572.     (*env)->subject = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1573.     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
  1574.     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
  1575.     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
  1576.     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
  1577.     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
  1578.     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
  1579.     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1580.     (*env)->message_id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1581.     if (**txtptr != ')') {
  1582.       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
  1583.       mm_log (LOCAL->tmp,WARN);
  1584.     }
  1585.     else ++*txtptr;        /* skip past delimiter */
  1586.     break;
  1587.   case 'N':            /* if NIL */
  1588.   case 'n':
  1589.     ++*txtptr;            /* bump past "I" */
  1590.     ++*txtptr;            /* bump past "L" */
  1591.     break;
  1592.   default:
  1593.     sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
  1594.     mm_log (LOCAL->tmp,WARN);
  1595.     break;
  1596.   }
  1597. }
  1598.  
  1599. /* Mail Access Protocol parse address list
  1600.  * Accepts: MAIL stream
  1601.  *        current text pointer
  1602.  *        parsed reply
  1603.  * Returns: address list, NIL on failure
  1604.  *
  1605.  * Updates text pointer
  1606.  */
  1607.  
  1608. ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,char **txtptr,
  1609.                  IMAPPARSEDREPLY *reply)
  1610. {
  1611.   ADDRESS *adr = NIL;
  1612.   char c = **txtptr;        /* sniff at first character */
  1613.                 /* ignore leading spaces */
  1614.   while (c == ' ') c = *++*txtptr;
  1615.   ++*txtptr;            /* skip past open paren */
  1616.   switch (c) {
  1617.   case '(':            /* if envelope S-expression */
  1618.     adr = imap_parse_address (stream,txtptr,reply);
  1619.     if (**txtptr != ')') {
  1620.       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
  1621.       mm_log (LOCAL->tmp,WARN);
  1622.     }
  1623.     else ++*txtptr;        /* skip past delimiter */
  1624.     break;
  1625.   case 'N':            /* if NIL */
  1626.   case 'n':
  1627.     ++*txtptr;            /* bump past "I" */
  1628.     ++*txtptr;            /* bump past "L" */
  1629.     break;
  1630.   default:
  1631.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1632.     mm_log (LOCAL->tmp,WARN);
  1633.     break;
  1634.   }
  1635.   return adr;
  1636. }
  1637.  
  1638. /* Mail Access Protocol parse address
  1639.  * Accepts: MAIL stream
  1640.  *        current text pointer
  1641.  *        parsed reply
  1642.  * Returns: address, NIL on failure
  1643.  *
  1644.  * Updates text pointer
  1645.  */
  1646.  
  1647. ADDRESS *imap_parse_address (MAILSTREAM *stream,char **txtptr,
  1648.                  IMAPPARSEDREPLY *reply)
  1649. {
  1650.   ADDRESS *adr = NIL;
  1651.   ADDRESS *ret = NIL;
  1652.   ADDRESS *prev = NIL;
  1653.   char c = **txtptr;        /* sniff at first address character */
  1654.   switch (c) {
  1655.   case '(':            /* if envelope S-expression */
  1656.     while (c == '(') {        /* recursion dies on small stack machines */
  1657.       ++*txtptr;        /* skip past open paren */
  1658.       if (adr) prev = adr;    /* note previous if any */
  1659.       adr = mail_newaddr ();    /* instantiate address and parse its fields */
  1660.       adr->personal = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1661.       adr->adl = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1662.       adr->mailbox = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1663.       adr->host = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1664.       if (**txtptr != ')') {    /* handle trailing paren */
  1665.     sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
  1666.     mm_log (LOCAL->tmp,WARN);
  1667.       }
  1668.       else ++*txtptr;        /* skip past close paren */
  1669.       c = **txtptr;        /* set up for while test */
  1670.                 /* ignore leading spaces in front of next */
  1671.       while (c == ' ') c = *++*txtptr;
  1672.       if (!ret) ret = adr;    /* if first time note first adr */
  1673.                 /* if previous link new block to it */
  1674.       if (prev) prev->next = adr;
  1675.     }
  1676.     break;
  1677.  
  1678.   case 'N':            /* if NIL */
  1679.   case 'n':
  1680.     *txtptr += 3;        /* bump past NIL */
  1681.     break;
  1682.   default:
  1683.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1684.     mm_log (LOCAL->tmp,WARN);
  1685.     break;
  1686.   }
  1687.   return ret;
  1688. }
  1689.  
  1690. /* Mail Access Protocol parse flags
  1691.  * Accepts: current message cache
  1692.  *        current text pointer
  1693.  *
  1694.  * Updates text pointer
  1695.  */
  1696.  
  1697. void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,char **txtptr)
  1698. {
  1699.   char *flag;
  1700.   char c;
  1701.   elt->valid = T;        /* mark have valid flags now */
  1702.   elt->user_flags = NIL;    /* zap old flag values */
  1703.   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = NIL;
  1704.   while (T) {            /* parse list of flags */
  1705.     flag = ++*txtptr;        /* point at a flag */
  1706.                 /* scan for end of flag */
  1707.     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
  1708.     c = **txtptr;        /* save delimiter */
  1709.     **txtptr = '\0';        /* tie off flag */
  1710.     if (*flag != '\0') {    /* if flag is non-null */
  1711.       if (*flag == '\\') {    /* if starts with \ must be sys flag */
  1712.     if (!strcmp (ucase (flag),"\\SEEN")) elt->seen = T;
  1713.     else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
  1714.     else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
  1715.     else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
  1716.     else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
  1717.     else if (!strcmp (flag,"\\JUNKED"));
  1718.                 /* coddle TOPS-20 server */
  1719.     else if (strcmp (flag,"\\XXXX") && strcmp (flag,"\\YYYY") &&
  1720.          strncmp (flag,"\\UNDEFINEDFLAG",14)) {
  1721.       sprintf (LOCAL->tmp,"Unknown system flag: %.80s",flag);
  1722.       mm_log (LOCAL->tmp,WARN);
  1723.     }
  1724.       }
  1725.                 /* otherwise user flag */
  1726.       else imap_parse_user_flag (stream,elt,flag);
  1727.     }
  1728.     if (c == ')') break;    /* quit if end of list */
  1729.   }
  1730.   ++*txtptr;            /* bump past delimiter */
  1731.   if (!elt->seen) {        /* if \Seen went off */
  1732.                 /* flush any cached text */
  1733.     if (elt->data2) fs_give ((void **) &elt->data2);
  1734.     if (!stream->scache) map_gc_body (mail_lelt (stream,elt->msgno)->body);
  1735.   }
  1736.   mm_flags (stream,elt->msgno);    /* make sure top level knows */
  1737. }
  1738.  
  1739. /* Mail Access Protocol parse user flag
  1740.  * Accepts: message cache element
  1741.  *        flag name
  1742.  */
  1743.  
  1744. void imap_parse_user_flag (MAILSTREAM *stream,MESSAGECACHE *elt,char *flag)
  1745. {
  1746.   long i;
  1747.                   /* sniff through all user flags */
  1748.   for (i = 0; i < NUSERFLAGS; ++i)
  1749.                   /* match this one? */
  1750.     if (!strcmp (flag,stream->user_flags[i])) {
  1751.       elt->user_flags |= 1 << i;/* yes, set the bit for that flag */
  1752.       return;            /* and quit */
  1753.     }
  1754.   sprintf (LOCAL->tmp,"Unknown user flag: %.80s",flag);
  1755.   mm_log (LOCAL->tmp,WARN);
  1756. }
  1757.  
  1758. /* Mail Access Protocol parse string
  1759.  * Accepts: MAIL stream
  1760.  *        current text pointer
  1761.  *        parsed reply
  1762.  *        flag that it may be kept outside of free storage cache
  1763.  * Returns: string
  1764.  *
  1765.  * Updates text pointer
  1766.  */
  1767.  
  1768. char *imap_parse_string (MAILSTREAM *stream,char **txtptr,
  1769.              IMAPPARSEDREPLY *reply,long special)
  1770. {
  1771.   char *st;
  1772.   char *string = NIL;
  1773.   unsigned long i;
  1774.   char c = **txtptr;        /* sniff at first character */
  1775.   mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
  1776.                 /* ignore leading spaces */
  1777.   while (c == ' ') c = *++*txtptr;
  1778.   st = ++*txtptr;        /* remember start of string */
  1779.   switch (c) {
  1780.   case '"':            /* if quoted string */
  1781.     i = 1;            /* initial byte count */
  1782.     while (**txtptr != '"') {    /* search for end of string */
  1783.       ++i;            /* bump count */
  1784.       ++*txtptr;        /* bump pointer */
  1785.     }
  1786.     **txtptr = '\0';        /* tie off string */
  1787.     string = (char *) fs_get (i);
  1788.     strncpy (string,st,i);    /* copy the string */
  1789.     ++*txtptr;            /* bump past delimiter */
  1790.     break;
  1791.   case 'N':            /* if NIL */
  1792.   case 'n':
  1793.     ++*txtptr;            /* bump past "I" */
  1794.     ++*txtptr;            /* bump past "L" */
  1795.     break;
  1796.  
  1797.   case '{':            /* if literal string */
  1798.                 /* get size of string */
  1799.     i = imap_parse_number (stream,txtptr);
  1800.                 /* have special routine to slurp string? */
  1801.     if (special && mg) string = (*mg) (tcp_getbuffer,LOCAL->tcpstream,i);
  1802.     else {            /* must slurp into free storage */
  1803.       string = (char *) fs_get (i + 1);
  1804.       *string = '\0';        /* init in case getbuffer fails */
  1805.                 /* get the literal */
  1806.       tcp_getbuffer (LOCAL->tcpstream,i,string);
  1807.     }
  1808.     fs_give ((void **) &reply->line);
  1809.                 /* get new reply text line */
  1810.     reply->line = tcp_getline (LOCAL->tcpstream);
  1811.     if (stream->debug) mm_dlog (reply->line);
  1812.     *txtptr = reply->line;    /* set text pointer to point at it */
  1813.     break;
  1814.   default:
  1815.     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
  1816.     mm_log (LOCAL->tmp,WARN);
  1817.     break;
  1818.   }
  1819.   return string;
  1820. }
  1821.  
  1822.  
  1823. /* Mail Access Protocol parse number
  1824.  * Accepts: MAIL stream
  1825.  *        current text pointer
  1826.  * Returns: number parsed
  1827.  *
  1828.  * Updates text pointer
  1829.  */
  1830.  
  1831. unsigned long imap_parse_number (MAILSTREAM *stream,char **txtptr)
  1832. {                /* parse number */
  1833.   long i = strtol (*txtptr,txtptr,10);
  1834.   if (i < 0) {            /* number valid? */
  1835.     sprintf (LOCAL->tmp,"Bad number: %ld",i);
  1836.     mm_log (LOCAL->tmp,WARN);
  1837.     i = 0;            /* make sure valid */
  1838.   }
  1839.   return (unsigned long) i;
  1840. }
  1841.  
  1842. /* Mail Access Protocol parse body structure or contents
  1843.  * Accepts: MAIL stream
  1844.  *        pointer to body pointer
  1845.  *        pointer to segment
  1846.  *        current text pointer
  1847.  *        parsed reply
  1848.  *
  1849.  * Updates text pointer, stores body
  1850.  */
  1851.  
  1852. void imap_parse_body (MAILSTREAM *stream,long msgno,BODY **body,char *seg,
  1853.               char **txtptr,IMAPPARSEDREPLY *reply)
  1854. {
  1855.   char *s;
  1856.   unsigned long i;
  1857.   BODY *b;
  1858.   PART *part;
  1859.   switch (*seg++) {        /* dispatch based on type of data */
  1860.   case '\0':            /* body structure */
  1861.     mail_free_body (body);    /* flush any prior body */
  1862.                 /* instantiate and parse a new body */
  1863.     imap_parse_body_structure (stream,*body = mail_newbody (),txtptr,reply);
  1864.     break;
  1865.   case '[':            /* body section text */
  1866.     if ((!(s = strchr (seg,']'))) || s[1]) {
  1867.       sprintf (LOCAL->tmp,"Bad body index: %.80s",seg);
  1868.       mm_log (LOCAL->tmp,WARN);
  1869.       return;
  1870.     }
  1871.     *s = '\0';            /* tie off section specifier */
  1872.                 /* get the body section text */
  1873.     s = imap_parse_string (stream,txtptr,reply,msgno);
  1874.     if (!(b = *body)) {        /* must have structure first */
  1875.       mm_log ("Body contents received when body structure unknown",WARN);
  1876.       if (s) fs_give ((void **) &s);
  1877.       return;
  1878.     }
  1879.                 /* get first section number */
  1880.     if (!(seg && *seg && ((i = strtol (seg,&seg,10)) > 0))) {
  1881.       mm_log ("Bogus section number",WARN);
  1882.       if (s) fs_give ((void **) &s);
  1883.       return;
  1884.     }
  1885.  
  1886.     do {            /* multipart content? */
  1887.       if (b->type == TYPEMULTIPART) {
  1888.     part = b->contents.part;/* yes, find desired part */
  1889.     while (--i && (part = part->next));
  1890.     if (!part || (((b = &part->body)->type == TYPEMULTIPART) && !(s&&*s))){
  1891.       mm_log ("Bad section number",WARN);
  1892.       if (s) fs_give ((void **) &s);
  1893.       return;
  1894.     }
  1895.       }
  1896.       else if (i != 1) {    /* otherwise must be section 1 */
  1897.     mm_log ("Invalid section number",WARN);
  1898.     if (s) fs_give ((void **) &s);
  1899.     return;
  1900.       }
  1901.                 /* need to go down further? */
  1902.       if (i = *seg) switch (b->type) {
  1903.       case TYPEMESSAGE:        /* embedded message, get body */
  1904.     b = b->contents.msg.body;
  1905.       case TYPEMULTIPART:    /* multipart, get next section */
  1906.     if ((*seg++ == '.') && (i = strtol (seg,&seg,10)) > 0) break;
  1907.       default:            /* bogus subpart */
  1908.     mm_log ("Invalid sub-section",WARN);
  1909.     if (s) fs_give ((void **) &s);
  1910.     return;
  1911.       }
  1912.     } while (i);
  1913.     if (b) switch (b->type) {    /* decide where the data goes based on type */
  1914.     case TYPEMULTIPART:        /* nothing to fetch with these */
  1915.       mm_log ("Textual body contents received for MULTIPART body part",WARN);
  1916.       if (s) fs_give ((void **) &s);
  1917.       return;
  1918.     case TYPEMESSAGE:        /* encapsulated message */
  1919.       if (b->contents.msg.text) fs_give ((void **) &b->contents.msg.text);
  1920.       b->contents.msg.text = s;
  1921.       break;
  1922.     case TYPETEXT:        /* textual data */
  1923.       if (b->contents.text) fs_give ((void **) &b->contents.text);
  1924.       b->contents.text = (unsigned char *) s;
  1925.       break;
  1926.     default:            /* otherwise assume it is binary */
  1927.       if (b->contents.binary) fs_give ((void **) &b->contents.binary);
  1928.       b->contents.binary = (void *) s;
  1929.       break;
  1930.     }
  1931.     break;
  1932.   default:            /* bogon */
  1933.     sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
  1934.     mm_log (LOCAL->tmp,WARN);
  1935.     return;
  1936.   }
  1937. }
  1938.  
  1939. /* Mail Access Protocol parse body structure
  1940.  * Accepts: MAIL stream
  1941.  *        current text pointer
  1942.  *        parsed reply
  1943.  *        body structure to write into
  1944.  *
  1945.  * Updates text pointer
  1946.  */
  1947.  
  1948. void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,char **txtptr,
  1949.                 IMAPPARSEDREPLY *reply)
  1950. {
  1951.   int i;
  1952.   char *s;
  1953.   PART *part = NIL;
  1954.   PARAMETER *param = NIL;
  1955.   char c = *((*txtptr)++);    /* grab first character */
  1956.                 /* ignore leading spaces */
  1957.   while (c == ' ') c = *((*txtptr)++);
  1958.   switch (c) {            /* dispatch on first character */
  1959.   case '(':            /* body structure list */
  1960.     if (**txtptr == '(') {    /* multipart body? */
  1961.       body->type= TYPEMULTIPART;/* yes, set its type */
  1962.       do {            /* instantiate new body part */
  1963.     if (part) part = part->next = mail_newbody_part ();
  1964.     else body->contents.part = part = mail_newbody_part ();
  1965.                 /* parse it */
  1966.     imap_parse_body_structure (stream,&part->body,txtptr,reply);
  1967.       } while (**txtptr == '(');/* for each body part */
  1968.       if (!(body->subtype =
  1969.         ucase (imap_parse_string (stream,txtptr,reply,(long)NIL))))
  1970.     mm_log ("Missing multipart subtype",WARN);
  1971.       if (**txtptr != ')') {    /* validate ending */
  1972.     sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
  1973.     mm_log (LOCAL->tmp,WARN);
  1974.       }
  1975.       else ++*txtptr;        /* skip past delimiter */
  1976.     }
  1977.  
  1978.     else {            /* not multipart, parse type name */
  1979.       if (**txtptr == ')') {    /* empty body? */
  1980.     ++*txtptr;        /* bump past it */
  1981.     break;            /* and punt */
  1982.       }
  1983.       body->type = TYPEOTHER;    /* assume unknown type */
  1984.       body->encoding = ENCOTHER;/* and unknown encoding */
  1985.                 /* parse type */
  1986.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1987.     ucase (s);        /* make parse easier */
  1988.     for (i=0;(i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]);i++);
  1989.     if (i <= TYPEMAX) {    /* only if found a slot */
  1990.       if (body_types[i]) fs_give ((void **) &s);
  1991.                 /* assign empty slot */
  1992.       else body_types[i] = cpystr (s);
  1993.       body->type = i;    /* set body type */
  1994.     }
  1995.       }
  1996.       body->subtype =        /* parse subtype */
  1997.     ucase (imap_parse_string (stream,txtptr,reply,(long) NIL));
  1998.  
  1999.       body->parameter = NIL;    /* init parameter list */
  2000.  
  2001.       c = *(*txtptr)++;        /* sniff at first character */
  2002.                 /* ignore leading spaces */
  2003.       while (c == ' ') c = *(*txtptr)++;
  2004.                 /* parse parameter list */
  2005.       if (c == '(') while (c != ')') {
  2006.     if (body->parameter)    /* append new parameter to tail */
  2007.       param = param->next = mail_newbody_parameter ();
  2008.     else body->parameter = param = mail_newbody_parameter ();
  2009.     if (!(param->attribute = imap_parse_string (stream,txtptr,reply,
  2010.                             (long) NIL))){
  2011.       mm_log ("Missing parameter attribute",WARN);
  2012.       break;
  2013.     }
  2014.     if (!(param->value = imap_parse_string (stream,txtptr,reply,
  2015.                         (long) NIL))) {
  2016.       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",
  2017.            param->attribute);
  2018.       mm_log (LOCAL->tmp,WARN);
  2019.       break;
  2020.     }
  2021.     switch (c = **txtptr) {    /* see what comes after */
  2022.     case ' ':        /* flush whitespace */
  2023.       while ((c = *++*txtptr) == ' ');
  2024.       break;
  2025.     case ')':        /* end of attribute/value pairs */
  2026.       ++*txtptr;        /* skip past closing paren */
  2027.       break;
  2028.     default:
  2029.       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",s);
  2030.       mm_log (LOCAL->tmp,WARN);
  2031.       break;
  2032.     }
  2033.       }
  2034.       else {            /* empty parameter, must be NIL */
  2035.     if (((c == 'N') || (c == 'n')) &&
  2036.         ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
  2037.         ((s[1] == 'L') || (s[1] == 'l')) && (s[2] == ' ')) *txtptr += 2;
  2038.     else {
  2039.       sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,s);
  2040.       mm_log (LOCAL->tmp,WARN);
  2041.       break;
  2042.     }
  2043.       }
  2044.  
  2045.       body->id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  2046.       body->description = imap_parse_string (stream,txtptr,reply,(long) NIL);
  2047.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  2048.     ucase (s);        /* make parse easier */
  2049.                 /* search for body encoding */
  2050.     for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
  2051.          strcmp (s,body_encodings[i]); i++);
  2052.     if (i > ENCMAX) body->type = ENCOTHER;
  2053.     else {            /* only if found a slot */
  2054.       if (body_encodings[i]) fs_give ((void **) &s);
  2055.                 /* assign empty slot */
  2056.       else body_encodings[i] = cpystr (s);
  2057.       body->encoding = i;    /* set body encoding */
  2058.     }
  2059.       }
  2060.                 /* parse size of contents in bytes */
  2061.       body->size.bytes = imap_parse_number (stream,txtptr);
  2062.       switch (body->type) {    /* possible extra stuff */
  2063.       case TYPEMESSAGE:        /* message envelope and body */
  2064.     if (strcmp (body->subtype,"RFC822")) break;
  2065.     imap_parse_envelope (stream,&body->contents.msg.env,txtptr,reply);
  2066.     body->contents.msg.body = mail_newbody ();
  2067.     imap_parse_body_structure(stream,body->contents.msg.body,txtptr,reply);
  2068.                 /* drop into text case */
  2069.       case TYPETEXT:        /* size in lines */
  2070.     body->size.lines = imap_parse_number (stream,txtptr);
  2071.     break;
  2072.       default:            /* otherwise nothing special */
  2073.     break;
  2074.       }
  2075.       if (**txtptr != ')') {    /* validate ending */
  2076.     sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
  2077.     mm_log (LOCAL->tmp,WARN);
  2078.       }
  2079.       else ++*txtptr;        /* skip past delimiter */
  2080.     }
  2081.     break;
  2082.  
  2083.   case 'N':            /* if NIL */
  2084.   case 'n':
  2085.     ++*txtptr;            /* bump past "I" */
  2086.     ++*txtptr;            /* bump past "L" */
  2087.     break;
  2088.   default:            /* otherwise quite bogus */
  2089.     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
  2090.     mm_log (LOCAL->tmp,WARN);
  2091.     break;
  2092.   }
  2093. }
  2094.